package de.ff7rl.core.tools;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.GridLayout;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;

import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.border.TitledBorder;
import javax.swing.filechooser.FileNameExtensionFilter;

import com.thoughtworks.xstream.XStream;

import de.ff7rl.core.GameInfo;
import de.ff7rl.core.map.DungeonMap;
import de.ff7rl.core.map.DungeonMapLine;
import de.ff7rl.core.map.Tile;
import de.ff7rl.core.reference.WeakReference;

public class MapEditor extends JFrame {

  private static class TileEditor extends JDialog {

    private Tile tile;

    public TileEditor(final MapEditor theMapEditor, final Tile theTile) {
      super(theMapEditor, "TileEditor", true);
      tile = theTile;
      setLayout(new VerticalLayout(5, VerticalLayout.BOTH, VerticalLayout.CENTER));
      final JPanel templatePanel = new JPanel(new GridLayout());
      templatePanel.setBorder(new TitledBorder("Template:"));
      final JComboBox templateComboBox = new JComboBox(getTemplyteComboBoxValues());
      templatePanel.add(templateComboBox);
      add(templatePanel);
      final JPanel customPanel = new JPanel(new GridLayout(0, 1));
      customPanel.setBorder(new TitledBorder("Custom:"));
      final JPanel charPanel = new JPanel(new GridLayout(0, 2));
      charPanel.add(new JLabel("Character:"));
      final JTextField charTextField = new JTextField(tile.getCharacter());
      charTextField.addActionListener(new ActionListener() {
        @Override
        public void actionPerformed(final ActionEvent theE) {
          templateComboBox.setSelectedIndex(0);
          tile.setCharacter(charTextField.getText().substring(0, 1));
        }
      });
      charPanel.add(charTextField);
      customPanel.add(charPanel);
      final JPanel foregroundPanel = new JPanel(new GridLayout(0, 2));
      foregroundPanel.add(new JLabel("Foreground color:"));
      foregroundPanel.add(new JLabel());
      customPanel.add(foregroundPanel);
      final JPanel backgroundPanel = new JPanel(new GridLayout(0, 2));
      backgroundPanel.add(new JLabel("Background color:"));
      backgroundPanel.add(new JLabel());
      customPanel.add(backgroundPanel);
      final JPanel blocksPlayerPanel = new JPanel(new GridLayout(0, 2));
      blocksPlayerPanel.add(new JLabel("blocks Player:"));
      final JCheckBox blocksPlayerCheckBox = new JCheckBox("", tile.isBlocksPlayer());
      blocksPlayerCheckBox.addActionListener(new ActionListener() {
        @Override
        public void actionPerformed(final ActionEvent theE) {
          templateComboBox.setSelectedIndex(0);
          tile.setBlocksPlayer(blocksPlayerCheckBox.isSelected());
        }
      });
      blocksPlayerPanel.add(blocksPlayerCheckBox);
      customPanel.add(blocksPlayerPanel);
      add(customPanel);
      pack();
      templateComboBox.addActionListener(new ActionListener() {
        @Override
        public void actionPerformed(final ActionEvent theE) {
          final Object selectedItem = templateComboBox.getSelectedItem();
          if (!(selectedItem instanceof TileTemplate)) {
            return;
          }
          tile = ((TileTemplate) selectedItem).getTemplate();
          charTextField.setText(tile.getCharacter());
          blocksPlayerCheckBox.setSelected(tile.isBlocksPlayer());
        }
      });
      setResizable(false);
      setLocationRelativeTo(theMapEditor);
      setVisible(true);
    }

    public static Tile showTileEditor(final MapEditor theMapEditor, final Tile theTile) {
      return new TileEditor(theMapEditor, theTile).getTile();
    }

    private Object[] getTemplyteComboBoxValues() {
      final TileTemplate[] tileTemplates = TileTemplate.values();
      final Object[] values = new Object[tileTemplates.length + 1];
      values[0] = "- Custom -";
      System.arraycopy(tileTemplates, 0, values, 1, tileTemplates.length);
      return values;
    }

    private Tile getTile() {
      return tile;
    }

  }

  private final static File MAP_ROOT = new File(new File(System.getProperty("user.dir"),
      "src/main/resources/de/ff7rl/core"), "maps");

  private static final XStream XSTREAM = new XStream();
  private static MapEditor INSTANCE;
  private DungeonMap map;
  private File mapFile;
  private JTextField generalTabLinesTextField;
  private JTextField generalTabColumnsTextField;
  private JTextField generalTabOffsetXTextField;
  private JTextField generalTabOffsetYTextField;
  private JTextField generalTabNameTextField;
  private JPanel tilesTabPanel;

  public MapEditor() {
    super(String.format("%s MapEditor %s", GameInfo.getName(), GameInfo.getVersion()));
    INSTANCE = this;
    initializeView();
    updateView();
  }

  public static void main(final String[] args) throws Exception {
    SwingUtilities.invokeLater(new Runnable() {
      @Override
      public void run() {
        new MapEditor();
      }
    });
  }

  protected static String getFileName(final DungeonMap theMap) {
    return String.format("%s.map", WeakReference.toFileName(theMap.getName()));
  }

  private JPanel initializeGeneralTab() {
    final JPanel generalTab = new JPanel(new VerticalLayout(5, VerticalLayout.BOTH, VerticalLayout.TOP));
    final JPanel namePanel = new JPanel(new GridLayout());
    namePanel.setBorder(new TitledBorder("Name:"));
    generalTabNameTextField = new JTextField();
    generalTabNameTextField.setEditable(false);
    namePanel.add(generalTabNameTextField);
    generalTab.add(namePanel);
    final JPanel sizePanel = new JPanel(new GridLayout(0, 2));
    sizePanel.setBorder(new TitledBorder("Size:"));
    sizePanel.add(new JLabel("Lines:"));
    generalTabLinesTextField = new JTextField();
    generalTabLinesTextField.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed(final ActionEvent theE) {
        final int oldLines = map.getLines().size();
        final int newLines = Integer.parseInt(generalTabLinesTextField.getText());
        if (newLines < oldLines) {
          for (int i = 0; i < oldLines - newLines; i++) {
            map.getLines().remove(map.getLines().size() - 1);
          }
        } else if (newLines > oldLines) {
          final int columns = Integer.parseInt(generalTabColumnsTextField.getText());
          for (int i = 0; i < newLines - oldLines; i++) {
            final DungeonMapLine newLine = new DungeonMapLine(columns, new Tile());
            map.getLines().add(newLine);
          }
        } else {
          return;
        }
        saveChanges();
        updateView();
      }
    });
    sizePanel.add(generalTabLinesTextField);
    sizePanel.add(new JLabel("Columns:"));
    generalTabColumnsTextField = new JTextField();
    generalTabColumnsTextField.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed(final ActionEvent theE) {
        final int oldColumns = map.getLines().get(0).size();
        final int newColumns = Integer.parseInt(generalTabColumnsTextField.getText());
        if (newColumns < oldColumns) {
          for (final DungeonMapLine line : map.getLines()) {
            for (int i = 0; i < oldColumns - newColumns; i++) {
              line.getTiles().remove(line.size() - 1);
            }
          }
        } else if (newColumns > oldColumns) {
          for (final DungeonMapLine line : map.getLines()) {
            for (int i = 0; i < newColumns - oldColumns; i++) {
              line.getTiles().add(new Tile());
            }
          }
        } else {
          return;
        }
        saveChanges();
        updateView();
      }
    });
    sizePanel.add(generalTabColumnsTextField);
    generalTab.add(sizePanel);
    final JPanel offsetPanel = new JPanel(new GridLayout(0, 2));
    offsetPanel.setBorder(new TitledBorder("Offset:"));
    offsetPanel.add(new JLabel("X:"));
    generalTabOffsetXTextField = new JTextField();
    final ActionListener offsetActionListener = new ActionListener() {
      @Override
      public void actionPerformed(final ActionEvent theE) {
        final int offsetX = Integer.parseInt(generalTabOffsetXTextField.getText());
        final int offsetY = Integer.parseInt(generalTabOffsetYTextField.getText());
        map.setOffset(new Point(offsetX, offsetY));
        saveChanges();
      }
    };
    generalTabOffsetXTextField.addActionListener(offsetActionListener);
    offsetPanel.add(generalTabOffsetXTextField);
    offsetPanel.add(new JLabel("Y:"));
    generalTabOffsetYTextField = new JTextField();
    generalTabOffsetYTextField.addActionListener(offsetActionListener);
    offsetPanel.add(generalTabOffsetYTextField);
    generalTab.add(offsetPanel);
    return generalTab;
  }

  private Component initializeTilesTab() {
    tilesTabPanel = new JPanel();
    return new JScrollPane(tilesTabPanel);
  }

  private void initializeView() {
    setSize(800, 600);
    setDefaultCloseOperation(EXIT_ON_CLOSE);
    setLocationRelativeTo(null);
    setVisible(true);
    final JMenuBar menubar = new JMenuBar();
    final JMenu menu = new JMenu("Editor");
    final JMenuItem newMenuItem = new JMenuItem("New");
    newMenuItem.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed(final ActionEvent theE) {
        final String mapName = JOptionPane.showInputDialog(null, "Enter map name:", "New map",
            JOptionPane.QUESTION_MESSAGE);
        map = new DungeonMap(1, 1, new Tile());
        map.setName(mapName);
        mapFile = new File(MAP_ROOT, getFileName(map));
        saveChanges();
        updateView();
      }
    });
    menu.add(newMenuItem);
    final JMenuItem openMenuItem = new JMenuItem("Open");
    openMenuItem.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed(final ActionEvent theE) {
        final JFileChooser fileChooser = new JFileChooser(MAP_ROOT);
        fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
        fileChooser.setFileFilter(new FileNameExtensionFilter("ff7rl Map (*.map)", "map"));
        final int result = fileChooser.showOpenDialog(null);
        if (result != JFileChooser.APPROVE_OPTION) {
          return;
        }
        mapFile = fileChooser.getSelectedFile();
        try {
          map = (DungeonMap) XSTREAM.fromXML(new FileInputStream(mapFile));
          updateView();
        } catch (final Exception e) {
          e.printStackTrace();
          JOptionPane.showMessageDialog(null, String.format("Unable to load map from %s.", mapFile), "Error",
              JOptionPane.ERROR_MESSAGE);
          System.exit(1);
        }
      }
    });
    menu.add(openMenuItem);
    menubar.add(menu);
    setJMenuBar(menubar);
    final JTabbedPane tabbedPane = new JTabbedPane();
    tabbedPane.addTab("General", initializeGeneralTab());
    tabbedPane.addTab("Tiles", initializeTilesTab());
    add(tabbedPane, BorderLayout.CENTER);
  }

  private void saveChanges() {
    try {
      XSTREAM.toXML(map, new FileOutputStream(mapFile));
    } catch (final FileNotFoundException e) {
      e.printStackTrace();
      JOptionPane.showMessageDialog(null, String.format("Unable to save map to %s.", mapFile), "Error",
          JOptionPane.ERROR_MESSAGE);
    }
  }

  private void updateGeneralTab() {
    generalTabNameTextField.setText(map.getName());
    final int lines = map.getLines().size();
    final int columns = map.getLines().get(0).size();
    generalTabLinesTextField.setText(String.valueOf(lines));
    generalTabColumnsTextField.setText(String.valueOf(columns));
    final Point offset = map.getOffset();
    generalTabOffsetXTextField.setText(String.valueOf(offset.x));
    generalTabOffsetYTextField.setText(String.valueOf(offset.y));
  }

  private void updateTilesTab() {
    tilesTabPanel.removeAll();
    tilesTabPanel.setLayout(new GridLayout(0, map.getLines().get(0).size()));
    class TileButtonClickHandler implements ActionListener {
      private final DungeonMapLine line;
      private final int index;
      public TileButtonClickHandler(final DungeonMapLine theLine, final int theIndex) {
        line = theLine;
        index = theIndex;
      }
      @Override
      public void actionPerformed(final ActionEvent theE) {
        final Tile editedTile = TileEditor.showTileEditor(INSTANCE, line.getTiles().get(index));
        line.getTiles().set(index, editedTile);
        saveChanges();
        updateView();
      }
    };
    for (final DungeonMapLine line : map.getLines()) {
      for (int i = 0; i < line.getTiles().size(); i++) {
        final Tile tile = line.getTiles().get(i);
        final JButton tileButton = new JButton(tile.getCharacter());
        tileButton.setForeground(tile.getForeGroundColor());
        tileButton.setBackground(tile.getBackGroundColor());
        tileButton.addActionListener(new TileButtonClickHandler(line, i));
        tilesTabPanel.add(tileButton);
      }
    }
  }

  private void updateView() {
    if (map == null) {
      return;
    }
    updateGeneralTab();
    updateTilesTab();
  }

}
